package cn.jbolt.core.api;

import cn.hutool.core.util.ArrayUtil;
import cn.jbolt.core.api.httpmethod.*;
import cn.jbolt.core.base.JBoltMsg;
import cn.jbolt.core.cache.JBoltApplicationCache;
import cn.jbolt.core.common.enums.JBoltApplicationType;
import cn.jbolt.core.enumutil.JBoltEnum;
import cn.jbolt.core.kit.JBoltControllerKit;
import cn.jbolt.core.model.Application;
import cn.jbolt.core.util.JBoltMethodCaller;
import com.jfinal.aop.Interceptor;
import com.jfinal.aop.Invocation;
import com.jfinal.kit.JsonKit;
import com.jfinal.kit.StrKit;
import com.jfinal.log.Log;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;

/**
 * API接口专用拦截器
 * 1、用于拦截API请求，判断API Http请求的Header中必要参数和格式设置、开放API访问控制等
 * 2、处理接口跨域请求
 * 3、处理解析JWT出来设置到当前threadLocal中，如果是OPenAPI就默认不处理解析JWT
 * 4、处理具体接口GET、POST、DELETE、PUT等HttpMethod限制请求
 */
public class JBoltApiInterceptor implements Interceptor {
    private static final Log API_LOG = Log.getLog("JBoltApiLog");
    /**
     * 第三方应用调用接口携带Header中的APPID的key
     */
    private static final String APPID_KEY = "jboltappid";

    @Override
    public void intercept(Invocation inv) {
        if (!(inv.getController() instanceof JBoltApiBaseController)) {
            inv.invoke();
            return;
        }
        JBoltApiBaseController controller = (JBoltApiBaseController) inv.getController();
        Method actionMethod = inv.getMethod();
        // 处理跨域实际请求
        JBoltControllerKit.processCrossOrigin(controller, actionMethod);
        String reqMethod = controller.getRequest().getMethod();
        // 处理预检请求
        if (JBoltControllerKit.isOptions(reqMethod)) {
            controller.renderJson(JBoltApiRet.OPTIONS);
            return;
        }
        boolean isUncheckJBoltApi = actionMethod.isAnnotationPresent(UnCheckJBoltApi.class);
        // 处理不合法请求 method
        boolean reqMethodOk = checkRequestMethod(reqMethod, actionMethod);
        if (!reqMethodOk) {
            controller.renderJBoltApiRet(JBoltApiRet.HTTP_METHOD_ERROR);
            return;
        }
        String jboltAppId = null;
        // 如果有UnCheckJBoltApi注解 说明不强制校验必须header携带JBOLTAPI='true'调用标识
        if (isUncheckJBoltApi) {
            String urlJboltAppId = controller.getPara(APPID_KEY);
            String headerJboltAppId = controller.getHeader(APPID_KEY);
            if (StrKit.isBlank(urlJboltAppId) && StrKit.isBlank(headerJboltAppId)) {
                controller.renderJBoltApiRet(JBoltApiRet.NO_JBOLT_APPID_PARAM);
                return;
            }
            jboltAppId = StrKit.notBlank(urlJboltAppId) ? urlJboltAppId : headerJboltAppId;
        } else {
            // 必须在请求request的header中标明是JBOLTAPI="true";
            boolean isJboltApiRequest = JBoltControllerKit.isCallJBoltApi(controller);
            if (!isJboltApiRequest) {
                API_LOG.error("访问接口Header中未带JBOLTAPI='true'调用标识，不予处理");
                controller.renderJsonFail(JBoltMsg.NOT_ALLOWED_JBOLT_API);
                return;
            }
            // 判断Header里有没有设置JBOLT-APPID
            jboltAppId = controller.getHeader(APPID_KEY);
            if (StrKit.isBlank(jboltAppId)) {
                controller.renderJBoltApiRet(JBoltApiRet.NO_JBOLT_APPID);
                return;
            }
        }

        // 如果cache里拿不到 数据库里也没有了 返回失败消息
        Application application = JBoltApplicationCache.me.getApiCallApplication(jboltAppId);
        if (application == null) {
            controller.renderJBoltApiRet(JBoltApiRet.APPLICATION_NOT_EXIST(jboltAppId));
            return;
        }

        // 如果application被禁用了 返回失败消息
        if (!application.getEnable()) {
            controller.renderJBoltApiRet(JBoltApiRet.APPLICATION_NOT_ENABLE(application));
            return;
        }

        // app进入threadLocal
        JBoltApiKit.setApplication(application);
        int type = application.getType();
        JBoltApplicationType applicationType = JBoltEnum.getEnumObjectByValue(JBoltApplicationType.class, type);
        JBoltApiRet checkRet = null;
        switch (applicationType) {
            case WECHAT_XCX:
                // 如果是微信小程序
                try {
                    checkRet =  JBoltMethodCaller.call("cn.jbolt.wechat.util.JBoltWechatKit", "processWxaConfig",new Class[]{Application.class}, new Object[]{application});
                    if (checkRet.isFail()) {
                        controller.renderJBoltApiRet(checkRet);
                        return;
                    }
                    break;
                } catch (Exception e) {
                    controller.renderJBoltApiFail("未引入jbolt_wechat模块，无法处理该请求");
                    throw new RuntimeException("未引入jbolt_wechat模块，无法处理该请求", e);
                }
            case MP_H5:
                // 如果是微信小程序
                try {
                    checkRet =  JBoltMethodCaller.call("cn.jbolt.wechat.util.JBoltWechatKit", "processWechatConfig",new Class[]{Application.class}, new Object[]{application});
                    if (checkRet.isFail()) {
                        controller.renderJBoltApiRet(checkRet);
                        return;
                    }
                    break;
                } catch (Exception e) {
                    controller.renderJBoltApiFail("未引入jbolt_wechat模块，无法处理该请求");
                    throw new RuntimeException("未引入jbolt_wechat模块，无法处理该请求", e);
                }

            default:
                break;
        }
        try {
            boolean actionIsOpen = actionMethod.isAnnotationPresent(OpenAPI.class);
            boolean controllerIsOpen = controller.getClass().isAnnotationPresent(OpenAPI.class);
            // 判断如果action上带着OpenAPI 说明是公开接口 默认不校验JWT
            if (actionIsOpen||controllerIsOpen) {
                OpenAPI openAPI = actionIsOpen?actionMethod.getAnnotation(OpenAPI.class):controller.getClass().getAnnotation(OpenAPI.class);
                if (openAPI.parseJwtIfExists()) {
                    /*
                     * 如果JWT存在 就解析并设置给当前线程 用于接口既可以访客访问 又能登录用户访问 然后根据是否携带JWT后续判断执行不同处理和返回不同数据的场景
                     */
                    JBoltJwtParseRet jwtParseRet = JBoltApiJwtManger.me().getJwtParseRet(controller, false);
                    if (jwtParseRet.isOk()) {
                        JBoltApiKit.setJwtParseRet(jwtParseRet);
                    }
                }
                inv.invoke();
                return;
            }

            // 判断如果action上带着JBoltApplyJWT
            if (actionMethod.isAnnotationPresent(JBoltApplyJWT.class)) {
                JBoltApplyJWT jBoltApplyJWT = actionMethod.getAnnotation(JBoltApplyJWT.class);
                JBoltApiKit.setApplyJwtTTL(jBoltApplyJWT.value());
                inv.invoke();
                JBoltApiJwtManger.me().createJBoltApiTokenToResponse(controller, application,jBoltApplyJWT);
                return;
            }

            // 如果注解了不校验JBOltAPI格式 JWT也不解析了
            if (isUncheckJBoltApi) {
                inv.invoke();
                return;
            }

            // 如果没有带着ApplyApiToken 说明这是一个需要接口鉴权JWT的请求
            JBoltJwtParseRet jwtParseRet = JBoltApiJwtManger.me().getJwtParseRet(controller, false);
            if (jwtParseRet.isOk()) {
                JBoltApiKit.setJwtParseRet(jwtParseRet);
                boolean reApplyJwt = actionMethod.isAnnotationPresent(JBoltReApplyJWT.class);
                if(reApplyJwt){
                    JBoltReApplyJWT jBoltReApplyJWT = actionMethod.getAnnotation(JBoltReApplyJWT.class);
                    JBoltApiKit.setApplyJwtTTL(jBoltReApplyJWT.value());
                }
                inv.invoke();
                // 判断如果action上带着JBoltReApplyJWT 需要重新签发新的JWT 例如解绑和重新绑定
                if (reApplyJwt) {
                    JBoltApiJwtManger.me().createJBoltApiTokenToResponse(controller, application,
                            actionMethod.getAnnotation(JBoltReApplyJWT.class));
                }
            } else {
                API_LOG.error("访问API:[{}:{}-{}] API_USER:[{}]\n错误原因:{}", controller.getClass().getName(), actionMethod.getName(),
                        inv.getActionKey(), jwtParseRet.getUserIdToStr(), JsonKit.toJson(jwtParseRet.getApiRet()));
				/*if (isUncheckJBoltApi) {
					controller.renderH5PageFailRet(jwtParseRet.getApiRet());
				} else {
				}*/
                controller.renderJBoltApiRet(jwtParseRet.getApiRet());
            }
        } catch (Exception e) {
            e.printStackTrace();
            API_LOG.error("访问API:[{}:{}-{}]\n错误原因:{}", controller.getClass().getName(), actionMethod.getName(),
                    inv.getActionKey(), e.getMessage());
            controller.renderJBoltApiRet(JBoltApiRet.API_FAIL(e.getMessage()));
        }

    }

    /**
     * 检测method是否存在不按要求method请求的问题
     *
     * @param reqMethod
     * @param actionMethod
     * @return
     */
    private boolean checkRequestMethod(String reqMethod, Method actionMethod) {
        Annotation[] annotations = actionMethod.getAnnotations();
        // 如果没有注解 就不检测了
        if (ArrayUtil.isEmpty(annotations)) {
            return true;
        }
        // 拿到枚举method 如果是空系统没有设置那就可以访问
        HttpMethod httpMethod = JBoltEnum.getEnumObjectByName(HttpMethod.class, reqMethod);
        if (httpMethod == null) {
            return true;
        }
        // 优先判断@JBoltHttpMethod注解 其他的可以忽略
        if (actionMethod.isAnnotationPresent(JBoltHttpMethod.class)) {
            return checkRequestMethodByJBoltApiMethod(httpMethod, actionMethod.getAnnotation(JBoltHttpMethod.class));
        }
        boolean any = false;
        // 有任何一个 就要继续判断 一个都没有就返回true
        for (Annotation annotation : annotations) {
            if (annotation instanceof JBoltHttpGet || annotation instanceof JBoltHttpPost
                    || annotation instanceof JBoltHttpPut || annotation instanceof JBoltHttpDelete
                    || annotation instanceof JBoltHttpOptions || annotation instanceof JBoltHttpHead
                    || annotation instanceof JBoltHttpPatch || annotation instanceof JBoltHttpTrace) {
                any = true;
            }
        }
        if (!any) {
            return true;
        }

        // 如果没有@JBoltHttpMethod注解 再去根据当前的Method找找有没有注解
        boolean flag = true;
        switch (httpMethod) {
            case GET:
                flag = actionMethod.isAnnotationPresent(JBoltHttpGet.class);
                break;
            case POST:
                flag = actionMethod.isAnnotationPresent(JBoltHttpPost.class);
                break;
            case DELETE:
                flag = actionMethod.isAnnotationPresent(JBoltHttpDelete.class);
                break;
            case PUT:
                flag = actionMethod.isAnnotationPresent(JBoltHttpPut.class);
                break;
            case HEAD:
                flag = actionMethod.isAnnotationPresent(JBoltHttpHead.class);
                break;
            case TRACE:
                flag = actionMethod.isAnnotationPresent(JBoltHttpTrace.class);
                break;
            case PATCH:
                flag = actionMethod.isAnnotationPresent(JBoltHttpPatch.class);
                break;
            case CONNECT:
                flag = actionMethod.isAnnotationPresent(JBoltHttpConnect.class);
                break;
            case OPTIONS:
                flag = actionMethod.isAnnotationPresent(JBoltHttpOptions.class);
                break;
        }
        return flag;
    }

    /**
     * 判断是否符合限定JBoltHttpMethod
     *
     * @param reqMethod
     * @param jBoltHttpMethod
     * @return
     */
    private boolean checkRequestMethodByJBoltApiMethod(HttpMethod reqMethod, JBoltHttpMethod jBoltHttpMethod) {
        HttpMethod[] methods = jBoltHttpMethod.value();
        if (methods != null && methods.length > 0) {
            for (HttpMethod m : methods) {
                if (reqMethod.equals(m)) {// 只要有一个符合就过
                    return true;
                }
            }
        } else {
            return true;
        }
        return false;
    }
}
